package ezXpns.storage;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.UUID;

import com.google.gson.Gson;

/**
 * A storage engine that serializes its data as JSON documents
 * @param <T> the value type
 */

//@author A0088827L
public class JsonStorageEngine<T> implements StorageEngine<T> {

	private JsonStorageEngine(String path, Class<T> entryType) throws IOException {
		this(path, entryType, new Gson());
	}
	
	private JsonStorageEngine(String path, Class<T> entryType, Gson gson) throws IOException {
		this.entryType = entryType;
		this.gson = gson;
		this.path = path;
		this.dirty = false;
		
		try (FileReader streamReader = new FileReader(path);
		     BufferedReader bufferedReader = new BufferedReader(streamReader)) {
			
			String header = bufferedReader.readLine();
			int numEntries = Integer.parseInt(header);
			
			for(int i = 0; i < numEntries; ++i) {
				String line = bufferedReader.readLine();
				int separatorPos = line.indexOf(' ');
				//TODO: warning on invalid data
				if(separatorPos > 0) {
					UUID key = UUID.fromString(line.substring(0, separatorPos));
					T value = gson.fromJson(line.substring(separatorPos + 1), entryType);
					add(key, value);	
				}
			}
		}
		catch(FileNotFoundException e) {//tolerate file not found
		}
	}
	
	public static<T2> JsonStorageEngine<T2> open(String path, Class<T2> entryType) throws IOException {
		return new JsonStorageEngine<T2>(path, entryType);
	}
	
	public static<T2> JsonStorageEngine<T2> open(String path, Class<T2> entryType, Gson gson) throws IOException {
		return new JsonStorageEngine<T2>(path, entryType, gson);
	}

	@Override
	public void add(UUID id, T obj) {
		dirty = true;
		objects.put(id, obj);
	}

	@Override
	public T remove(UUID id) {
		T obj = get(id);
		
		if(obj != null) {
			dirty = true;
			objects.remove(id);
		}
		
		return obj;
	}

	@Override
	public T replace(UUID id, T newObj) {
		T oldObj = get(id);
		
		if(oldObj != null) {
			dirty = true;
			objects.put(id, newObj);
		}
		
		return oldObj;
	}

	@Override
	public T get(UUID id) {
		return objects.get(id);
	}

	@Override
	public Iterable<Entry<UUID, T>> getAllEntries() {
		return objects.entrySet();
	}

	@Override
	public void save() throws IOException {
		try (FileWriter streamWriter = new FileWriter(path);
		     BufferedWriter bufferedWriter = new BufferedWriter(streamWriter)) {
			
			Integer numEntries = objects.size();
			bufferedWriter.write(numEntries.toString());
			bufferedWriter.newLine();
			
			for(Entry<UUID, T> entry: objects.entrySet()) {
				bufferedWriter.write(entry.getKey().toString());
				bufferedWriter.write(" ");
				bufferedWriter.write(gson.toJson(entry.getValue(), entryType));
				bufferedWriter.newLine();
			}
		}
		
		dirty = false;
	}
	
	@Override
	public void close() throws IOException {
		if(dirty) save();
	}

	private HashMap<UUID, T> objects = new HashMap<UUID, T>();
	private Gson gson;
	private Class<T> entryType;
	private String path;
	private boolean dirty;
}
